/**
 * Copyright 2011 ArcBees Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */

package com.gwtserializer.dispatch.annotation.helper;


import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.Element;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.util.ElementFilter;
import javax.lang.model.util.Elements;

/**
 * {@link ReflectionHelper} is an internal class that provides common routines
 * only used by the annotation processors.
 * 
 * @author Brendan Doherty
 * @author Florian Sauter
 * @author Stephen Haberman (concept)
 */
@SuppressWarnings("unchecked")
public class ReflectionHelper {

	private TypeElement classRepresenter;
	private ProcessingEnvironment environment;

	public ReflectionHelper(ProcessingEnvironment environment, TypeElement classRepresenter) {
		this.classRepresenter = classRepresenter;
		this.environment = environment;
	} 

	public Collection<VariableElement> filterConstantFields(Collection<VariableElement> fieldElements) {
		return filterFields(fieldElements, Modifier.STATIC, Modifier.FINAL);
	}

	/**
	 * Returns only fields which are not annotated with one of the passed
	 * annotation.
	 */
	public Collection<VariableElement> filterFields(Collection<VariableElement> fieldElements,
			Class<? extends Annotation>... annotations) {
		Collection<VariableElement> filteredFields = new ArrayList<VariableElement>();
		filteredFields.addAll(fieldElements);
		for (VariableElement fieldElement : fieldElements) {
			for (Class<? extends Annotation> passedAnnotation : annotations) {
				Annotation fieldAnnotation = fieldElement.getAnnotation(passedAnnotation);
				if (fieldAnnotation != null) {
					filteredFields.remove(fieldElement);
					break;
				}
			}
		}
		return filteredFields;
	}

	/**
	 * Returns only fields which do not contain one of the passed modifiers.
	 */
	public Collection<VariableElement> filterFields(Collection<VariableElement> fieldElements, Modifier... modifiers) {
		Collection<VariableElement> filteredFields = new ArrayList<VariableElement>();
		filteredFields.addAll(fieldElements);
		for (VariableElement fieldElement : fieldElements) {
			for (Modifier modifier : modifiers) {
				if (fieldElement.getModifiers().contains(modifier)) {
					filteredFields.remove(fieldElement);
					break;
				}
			}
		}
		return filteredFields;
	}

	/**
	 * Returns only fields which simple names do not equal the passed field
	 * names.
	 */
	public Collection<VariableElement> filterFields(Collection<VariableElement> fieldElements,
			String... simpleFieldNames) {
		Collection<VariableElement> filteredFields = new ArrayList<VariableElement>();
		filteredFields.addAll(fieldElements);
		for (VariableElement fieldElement : fieldElements) {
			for (String simpleFieldName : simpleFieldNames) {
				if (fieldElement.getSimpleName().toString().equals(simpleFieldName)) {
					filteredFields.remove(fieldElement);
					break;
				}
			}
		}
		return filteredFields;
	}

	/**
	 * Returns all fields annotated with the passed annotation classes.
	 */
	public Collection<VariableElement> getAnnotatedFields(Class<? extends Annotation>... annotations) {
		Collection<VariableElement> fieldsCopy = getFields();
		for (Class<? extends Annotation> annotation : annotations) {
			Collection<VariableElement> nonAnnotatedFields = filterFields(getFields(), annotation);
			fieldsCopy.removeAll(nonAnnotatedFields);
		}
		return fieldsCopy;
	}

	/**
	 * Returns the class name.
	 * <p>
	 * For example:<br>
	 * {@code com.gwtserializer.dispatch.annotation.Foo}
	 * </p>
	 * 
	 * @return the class name.
	 */
	public String getClassName() {
		return getPackageName() + '.' + getSimpleClassName();
	}

	public TypeElement getClassRepresenter() {
		return classRepresenter;
	}

	/**
	 * Returns all fields ordered that are {@link Modifier.FINAL} or
	 * {@link Modifier.STATIC}.
	 */
	public Collection<VariableElement> getConstantFields() {
		return getModifierFields(Modifier.FINAL, Modifier.STATIC);
	}

	/**
	 * Returns all fields.
	 * <p>
	 * <b>Important:</b> Fields are not sorted according to @{@link Order}!
	 * </p>
	 * To get these sorted use {@link ReflectionHelper#getOrderedFields()}.
	 */
	public Collection<VariableElement> getFields() {
		List<? extends Element> members = getElementUtils().getAllMembers(classRepresenter);
		return ElementFilter.fieldsIn(members);
	}

	/**
	 * Returns all fields which contains {@link Modifier.FINAL}.
	 */
//	public Collection<VariableElement> getFinalFields() {
//		return filterFields(getOrderedFields(), Modifier.FINAL);
//	}

	/**
	 * Returns all fields annotated with @{@link In}. Sorted based on the @
	 * {@link In} annotation.
	 */
//	public Collection<VariableElement> getInFields() {
//		return sortFields(In.class, getAnnotatedFields(In.class));
//	}

	/**
	 * Returns all fields with the passed modifier.
	 */
	public Collection<VariableElement> getModifierFields(Modifier... modifiers) {
		Collection<VariableElement> modifierFields = new ArrayList<VariableElement>();
		modifierFields.addAll(getFields());
		for (Modifier modifier : modifiers) {
			Collection<VariableElement> nonModifierFields = filterFields(getFields(), modifier);
			modifierFields.removeAll(nonModifierFields);
		}
		return modifierFields;
	}

	/**
	 * Returns all fields that are not {@link Modifier.FINAL} or
	 * {@link Modifier.STATIC}. Sorted based on the @ {@link Order} annotation.
	 */
//	public Collection<VariableElement> getNonConstantFields() {
//		return filterFields(getOrderedFields(), Modifier.FINAL, Modifier.STATIC);
//	}

	/**
	 * Returns all non constant fields annotated with @{@link Optional}. Sorted
	 * based on the @ {@link Order} annotation.
	 */
//	public Collection<VariableElement> getOptionalFields() {
//		return sortFields(Order.class, filterConstantFields(getAnnotatedFields(Optional.class)));
//	}

	/**
	 * Returns all non constant fields annotated with passed annotation.
	 * <p>
	 * <b>Important:</b> Fields are not sorted!
	 * </p>
	 */
//	public Collection<VariableElement> getOptionalFields(Class<? extends Annotation> annotation) {
//		return filterConstantFields(getAnnotatedFields(Optional.class, annotation));
//	}

	/**
	 * Returns all fields ordered. Sorted based on the @ {@link Order}
	 * annotation.
	 */
//	public Collection<VariableElement> getOrderedFields() {
//		return sortFields(Order.class, getFields());
//	}

	/**
	 * Returns all fields annotated with @{@link Out}. Sorted based on the @
	 * {@link Out} annotation.
	 */
//	public Collection<VariableElement> getOutFields() {
//		return sortFields(Out.class, getAnnotatedFields(Out.class));
//	}

	public String getPackageName() {
		return getElementUtils().getPackageOf(classRepresenter).getQualifiedName().toString();
	}

	public ProcessingEnvironment getProcessingEnvironment() {
		return environment;
	}

	/**
	 * Returns all non {@link Optional}|{@link Modifier#STATIC}|
	 * {@link Modifier#FINAL} fields <b>ordered</b>. Sorted based on the @
	 * {@link Order} annotation.
	 * <p>
	 * Required are all:
	 * <ul>
	 * <li>Declared fields matching rules below</li>
	 * <li><b>Non</b> annotated {@link Optional} fields</li>
	 * <li><b>Non</b> {@link Modifier#STATIC} fields</li>
	 * <li><b>Non</b> {@link Modifier#FINAL} fields</li>
	 * </ul>
	 * </p>
	 */
//	public Collection<VariableElement> getRequiredFields() {
//		Collection<VariableElement> fields = getFields();
//		fields.removeAll(getOptionalFields());
//		fields = filterFields(fields, Modifier.FINAL, Modifier.STATIC);
//		return sortFields(Order.class, fields);
//		return fields;
//	}

	public String getSimpleClassName() {
		return classRepresenter.getSimpleName().toString();
	}

	/**
	 * Returns all fields which contains {@link Modifier.STATIC}.
	 */
//	public Collection<VariableElement> getStaticFields() {
//		return filterFields(getOrderedFields(), Modifier.STATIC);
//	}

	/**
	 * Sorts the passed fields based on the passed annotation sort logic.
	 */
//	public Collection<VariableElement> sortFields(Class<? extends Annotation> annotation,
//			Collection<VariableElement> fields) {
//		SortedMap<Integer, VariableElement> sortedFields = new TreeMap<Integer, VariableElement>();
//		if (In.class.equals(annotation)) {
//			sortInFields(sortedFields, fields);
//		} else if (Out.class.equals(annotation)) {
//			sortOutFields(sortedFields, fields);
//		} else if (Order.class.equals(annotation)) {
//			sortOrderFields(sortedFields, fields);
//		} else {
//			return fields;
//		}
//		return sortedFields.values();
//	}

//	public boolean hasOptionalFields() {
//		return getOptionalFields().size() > 0;
//	}
//
//	public boolean hasRequiredFields() {
//		return getRequiredFields().size() > 0;
//	}

	/**
	 * Utility method.
	 */
	protected Elements getElementUtils() {
		return environment.getElementUtils();
	}

//	protected void sortInFields(SortedMap<Integer, VariableElement> sortedFields, Collection<VariableElement> fields) {
//		for (VariableElement fieldElement : fields) {
//			In inFieldAnnotation = fieldElement.getAnnotation(In.class);
//			if (inFieldAnnotation != null) {
//				sortedFields.put(inFieldAnnotation.value(), fieldElement);
//			}
//		}
//	}
//
//	protected void sortOrderFields(SortedMap<Integer, VariableElement> sortedFields, Collection<VariableElement> fields) {
//		int maxOrderNum = -1;
//		for (VariableElement fieldElement : fields) {
//			Order order = fieldElement.getAnnotation(Order.class);
//			if (order != null) {
//				maxOrderNum = Math.max(maxOrderNum, order.value());
//			}
//		}
//
//		for (VariableElement fieldDecl : fields) {
//			Order order = fieldDecl.getAnnotation(Order.class);
//			if (order != null) {
//				maxOrderNum = Math.max(maxOrderNum, order.value());
//				sortedFields.put(order.value(), fieldDecl);
//			} else {
//				sortedFields.put(++maxOrderNum, fieldDecl);
//			}
//		}
//	}
//
//	protected void sortOutFields(SortedMap<Integer, VariableElement> sortedFields, Collection<VariableElement> fields) {
//		for (VariableElement fieldElement : fields) {
//			Out outFieldAnnotation = fieldElement.getAnnotation(Out.class);
//			if (outFieldAnnotation != null) {
//				sortedFields.put(outFieldAnnotation.value(), fieldElement);
//			}
//		}
//	}
}
